package com.southgis.ibase.sql.utils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.util.CollectionUtils;

import com.southgis.ibase.sql.exception.SQLAnalyseException;

/**
 * SQL语句处理类。与环境无关的较通用处理，更多功能实现在utilsRepository包
 */
public final class SqlUtil {

	/**
	 * 获取where in语句，主要处理oracle的sql语句in子句中（id in (1, 2, ..., 1000, 1001, ...)）；
	 * 如果子句中超过1000项就会报错。 这主要是oracle考虑性能问题做的限制。 如果要解决次问题，可以用：<br>
	 * id (1, 2, ..., 1000) or id (1001, ...) <br>
	 * 如果values为empty，将生成一个空值的in条件：(name in (''))
	 * @param column 字段名
	 * @param values 值集合，不能为null。
	 * @param num 指明每组几个元素，如果小于等于0，使用默认值（目前为1000）
	 * @return where in 语句，即返回( column in (...) or column in (...) )字符串
	 * @see 类似实现参见：com.southgis.ibase.utils.dao.DatabaseUtil#SplitListSqlIn
	 */
	public static String getWhereInValuesSql(String column, List<String> values, int num)
	{
		if(num<=0)
			num=1000;
		// sql语句
		StringBuilder sql = new StringBuilder("(");
		// 值的个数
		int valueSize = values.size();
		if(valueSize==0){
			sql.append(column).append(" in (''");
		}else {
			// 批次数
			int batchSize = valueSize / num;
			if(valueSize % num != 0)
				batchSize++;
			for (int i = 0; i < batchSize; i++) {
				if (i > 0) {
					sql.append(") or ");
				}
				sql.append(column).append(" in (");
				int curNum=i * num;
				for (int j = curNum; j < curNum+num && j < valueSize; j++) {
					if (j > curNum) {
						sql.append(",");
					}
					sql.append("'").append(toSql(values.get(j))).append("'");
				}
			}
		}
		sql.append("))");
		return sql.toString();
	}
	
	/**
	 * 移除sql语句中的字符串常量内容。主要方便后续的其它处理
	 * @param sql 要处理的sql语句，会将单引号包含的内容将每个字符替换成*。
	 * @param saveQuot 是否要保留包含内容的本单引号（内容中的单引号仍会被替换），如果为false，则单引号也会被替换。
	 * @return
	 */
	public static String removeSqlStringConst(String sql,boolean saveQuot)
	{
		if(StringUtils.isEmpty(sql)) return "";
		StringBuilder sbSql=new StringBuilder(sql.length());
		
		boolean bIn=false;
		for(int inx=0;inx<sql.length();++inx){
			char chx=sql.charAt(inx);
			if(chx!='\''){
				if(bIn)
					sbSql.append('*');
				else
					sbSql.append(chx);
			}
			else if(bIn){
				int iback=inx+1;
				if(sql.length()==iback || sql.charAt(iback)!='\''){
					sbSql.append(saveQuot?chx:'*');
					bIn=false;
				}else{
					sbSql.append("**");
					inx=iback;
				}
			}else{
				sbSql.append(saveQuot?chx:'*');
				bIn=true;
			}
		}
		return sbSql.toString();
	}
	
	
	/**
	 * 转换为sql常量字符串值（替换'为''）。注意：返回值不会添加单引号。
	 * @param text
	 * @return 如果为空（Empty（“”）或null），返回Empty
	 */
	public static String toSql(String text){
		if(StringUtils.isEmpty(text))
			return "";
		return text.replace("'", "''");
	}

	/**
	 * 获得sql语句中的命令关键字（小写）。
	 * 如果sql是复合语句，仅返回开头的（主语句）关键字。
	 * @param sql
	 * @return sql语句中的命令关键字（select、update、insert、delete等）
	 */
	@SuppressWarnings("deprecation")
	public static String getSqlCmd(String sql) {
		if (StringUtils.isEmpty(sql)) {
			return "";
		}

		// 找到非空的第一个字符
		int is = 0;
		while (is < sql.length() && Character.isSpace(sql.charAt(is)))
			is++;
		// 找到下一个空白符
		int ie = is;
		while (ie < sql.length() && !Character.isSpace(sql.charAt(ie)))
			ie++;
		return sql.substring(is, ie).toLowerCase();
	}

	/**
	 * 判断sql语句中是否包含关键字，并返回所在位置
	 * @param sSql
	 * @param sKey 要查找的关键（比如：select、order by、where等），不区分大小写
	 * @return 关键字所在位置，-1表示未找到
	 */
	@SuppressWarnings("deprecation")
	public static int existKeyword(String sSql, String sKey) {
		if (StringUtils.isEmpty(sSql)) return -1;
		String sTemp = " " + sSql.toLowerCase() + " ";
		sKey = sKey.toLowerCase();

		String sPart;
		int is = 0;
		int ie = 0;
		int iPos;
		while (is >= 0) {
			ie = sTemp.indexOf("'", is);
			if (ie < 0) {
				sPart = sTemp.substring(is);
			} else {
				sPart = sTemp.substring(is, ie);
			}

			iPos = sPart.indexOf(sKey);
			if (iPos > 0 && Character.isSpace(sPart.charAt(iPos - 1)) && Character.isSpace(sPart.charAt(iPos + sKey.length())))
				return iPos + is - 1;

			is = -1;
			if (ie >= 0) {
				is = sTemp.indexOf("'", ie + 1);
			}
			if (is >= 0) is++;
		}
		return -1;
	}

	/**
	 * 将列表集合拆分为最大1000个元素的集合数组，用于SQL语句的in查询（Oracle限制最大1000个元素）。<br>
	 * 不会返回null（如果listSrc为null或isEmpty，则返回长度为0的数组）。
	 * @param listSrc 列表集合
	 * @return
	 */
	public static <T> List<T>[] splitListSqlIn(List<T> listSrc) {
		int count = 0;
		if (!CollectionUtils.isEmpty(listSrc)) {
			count = listSrc.size() / 1000;
			if (count * 1000 < listSrc.size())
				count++;
		}
		@SuppressWarnings("unchecked")
		List<T>[] splitList = new List[count];
		if (count == 0) return splitList;

		int splitIndex = 0;
		int startPos = 0;
		int endPos = 1000;
		//(dennis)in查询中，一次查询的元素不要超过1000
		while (endPos < listSrc.size()) {
			splitList[splitIndex] = listSrc.subList(startPos, endPos);
			startPos = endPos;
			endPos += 1000;
			splitIndex++;
		}
		if (startPos > 0) {//等于0，表示没有拆分过元素，所以不要额外取子集；大于0，则取余下的子集
			splitList[splitIndex] = listSrc.subList(startPos, listSrc.size());
		} else {
			splitList[splitIndex] = listSrc;
		}

		return splitList;
	}

	/**
	 * 格式化sql语句：1，将字符串内容提取出来，并用占位符“{＜n＞}”替换<br>
	 * （注：＜＞是英文半角小于/大于符；n是序号，从0开始）；<br>
	 * 2，将回车换行Tab空白符全部转换为空格。
	 * @param sql 要处理的sql语句
	 * @return Key为格式化过的sql语句，Value为替换的占位符信息（其中Key为序号，Value为原sql字符串内容，含单引号）
	 */
	public static Pair<String, Map<Integer, String>> formatSql(String sql) {
		if (StringUtils.isEmpty(sql)) {
			return Pair.of("", null);
		}
		StringBuilder newSql = new StringBuilder();
		//处理单引号里面的字符串
		int start = sql.indexOf("'");
		int end = 0;
		int inxToken = 0;
		Map<Integer, String> contentMap = new HashMap<>();
		while (start >= 0) {
			newSql.append(sql.substring(end, start));
			newSql.append("{<").append(inxToken).append(">}");
			end = sql.indexOf("'", start + 1);
			if (end < 0) {//缺失单引号，语句已不正确
				throw new SQLAnalyseException("sql语句不正确，缺少与位置"+start+"处匹配的引号");
			}
			end++;
			contentMap.put(inxToken, sql.substring(start, end));
			inxToken++;
			start = sql.indexOf("'", end);
		}
		if (end >= 0 && end < sql.length())
			newSql.append(sql.substring(end));
		//替换所有空白符为空格，以方便处理
		String key=newSql.toString().replaceAll("[\\\r\\\n\\\t\\\f]", " ");

		return Pair.of(key, contentMap);
	}
	
	/**
	 * 将{@link #formatSql}所分处理的sql语句，重新合并为完整sql语句。
	 * @param partSql Key为格式化过的sql语句，Value为替换的占位符信息（其中Key为序号，Value为原sql字符串内容，含单引号）
	 * @return
	 */
	public static String mergeSql(Pair<String, Map<Integer, String>> partSql)
	{
		StringBuilder newSql = new StringBuilder();
		String sql=partSql.getKey();
		int start = sql.indexOf("{<");
		int end = 0;
		Integer inxToken = 0;
		while(start>=0)
		{
			newSql.append(sql.substring(end, start));
			start+=2;
			end=sql.indexOf(">}",start);
			if (end < 0) break;
			inxToken=Integer.parseInt(sql.substring(start,end));
			newSql.append(partSql.getValue().get(inxToken));
			end+=2;
			start = sql.indexOf("{<",end);
		}
		if (end >= 0 && end < sql.length())
			newSql.append(sql.substring(end));
		
		return newSql.toString();
	}

}
